import cx from 'classnames';
import debounce from 'lodash.debounce';
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';

import { getCoreRowModel, useReactTable } from '@tanstack/react-table';
import { Scrollbars as OriginalScrollbars } from 'react-custom-scrollbars';
import DefaultStatus from '../../../DefaultStatus';
import TableContext from '../TableContext';
import { TablePagination, TableSummary, TableTbody, TableThead } from './index';

import { prefix } from '../../../config';
import { DISPLAY_TABLE, defaultTableSizeConfig } from '../../config';
import { UPDATE_STATE, initScrollInfo } from '../../context/table';
import '../../style/EditableTable.less';
import * as utils from '../../utils';

import type { ColumnDef, TableOptions } from '@tanstack/react-table';
import type { ScrollbarProps } from 'react-custom-scrollbars';
import type { defaultStatusRef } from '../../../DefaultStatus';
import type { Context, ScrollInfo } from '../../context/table';
import type {
  AnyObject,
  Column,
  ColumnPinning,
  ColumnPinningInfo,
  ColumnSizes,
  BaseTableProps as Props,
  TableContextRef,
} from '../../type';
import type { SettingCheckBoxInfo } from '../DisplayTable';

const Scrollbars =
  OriginalScrollbars as unknown as React.ComponentClass<ScrollbarProps>;

function BaseTable<T extends AnyObject>(
  props: Props<T>,
  ref?: React.ForwardedRef<TableContextRef<T>>,
) {
  const {
    size = 'large',
    bordered = true,
    columns = [],
    dataSource = [],
    pagination = false,
  } = props;
  const { prefixCls = prefix.editableTable, context } = props;
  const { id, isCanResizeColumn, isShowSummary, tableStyle, defaultStatus } =
    props;

  const {
    columnPinning,
    dragTypes,
    isFixedHeight,
    columnSummary,
    columnVisibility,
  } = props;
  const tableId = useMemo(() => id || utils.createTableId('table'), [id]);
  const tableDom = document.getElementById(tableId);
  const { table: tableContextInfo, dispatch } = useContext<Context<T>>(context);
  const theadRef = useRef<any>();
  const tbodyRef = useRef<any>();
  const summaryRef = useRef<any>();
  const defaultStatusRef = useRef<defaultStatusRef>();

  const [isShowBoxShadow, setIsShowBoxShadow] = useState(false);
  const [borderShadow, setBorderShadow] = useState({
    left: false,
    right: false,
    bottom: false,
  }); // 表格边框shadow
  const [columnPinningInfo, setColumnPinningInfo] = useState<ColumnPinningInfo>(
    { left: {}, right: {} },
  ); // 固定列偏移样式信息

  const columnSizes = useMemo(
    () =>
      utils.getVisibleColumnSize(
        utils.getAllFlatColumns(columns),
        columnVisibility,
      ),
    [columns, columnVisibility],
  );
  const [tableColumns, setTableColumns] = useState<Column<T>[]>([]);
  const handleTableColumns = (
    columns: ColumnDef<T>[],
    columnSizes: ColumnSizes<T>,
    columnVisibility: SettingCheckBoxInfo,
    columnPinning: ColumnPinning,
  ) => {
    if (!!columns.length) {
      const totalWidth = utils.getElementSize(tableDom, 'width');
      const tableColumns = utils.setColumnSize(
        columns,
        columnSizes,
        columnVisibility,
        totalWidth,
      );
      setTableColumns(utils.adjustColumnOrder(tableColumns, columnPinning));
    }
  };

  const tableColumnsDeps = [
    columns,
    columnSizes,
    columnVisibility,
    columnPinning,
  ];
  useEffect(() => {
    handleTableColumns(columns, columnSizes, columnVisibility, columnPinning);
  }, [...tableColumnsDeps, tableDom]);
  useEffect(() => {
    const event = debounce(
      handleTableColumns.bind(this, ...tableColumnsDeps),
      300,
    );
    window.addEventListener('resize', event);
    return () => window.removeEventListener('resize', event);
  }, [...tableColumnsDeps, tableDom]);

  // 表格tbody高度
  const tbodyHeightInfo = useMemo(() => {
    const height = utils.getElementSize(
      tableDom && tableDom.parentElement,
      'height',
    );
    const {
      thead,
      tbody,
      summary,
      pagination: paginationHeight,
    } = defaultTableSizeConfig[size];
    const realTimeHeight = dataSource.length * tbody;
    const maxHeight =
      height -
      thead -
      (!!realTimeHeight && isShowSummary ? summary : 0) -
      (pagination ? paginationHeight : 0);
    let tbodyHeight = 0;

    if (realTimeHeight <= 0 && defaultStatusRef.current) {
      const { height } = defaultStatusRef.current.getSize();
      tbodyHeight = maxHeight > height ? height : maxHeight;
    } else {
      tbodyHeight =
        maxHeight > 0 && realTimeHeight > maxHeight
          ? maxHeight
          : realTimeHeight;
    }
    return { tbodyHeight, maxHeight, realTimeHeight };
  }, [
    defaultTableSizeConfig,
    size,
    isShowSummary,
    dataSource,
    tableDom,
    pagination,
  ]);

  useEffect(() => {
    setTimeout(() => {
      const scroll = handleScrollInfo(isShowSummary);
      handleBorderShadow(scroll);
    }, 300);
  }, [isShowSummary, tbodyRef.current, summaryRef.current]);
  const resizeDeps = [
    columnVisibility,
    columnPinning,
    isShowSummary,
    tableDom,
    tbodyHeightInfo,
    size,
  ];
  useEffect(
    () => onResize(),
    [...resizeDeps, ...utils.getColumnPinningDom(columnPinning)],
  );
  useEffect(() => {
    const event = debounce(onResize, 300);
    window.addEventListener('resize', event);
    return () => window.removeEventListener('resize', event);
  }, [...resizeDeps, ...utils.getColumnPinningDom(columnPinning)]);
  const onResize = () => {
    !!tableColumns.length &&
      setColumnPinningInfo(
        utils.getColumnPinningInfo(columnPinning, tableColumns),
      );
    handleBoxShadow(isShowSummary);
    handleScrollInfo(isShowSummary);
    setTableStyle(tableDom);
  };
  const setTableStyle = (tableDom: HTMLElement) => {
    const width = utils.getElementSize(tableDom, 'width');
    const height = utils.getElementSize(tableDom, 'height');
    const maxHeight = utils.getElementSize(
      tableDom && tableDom.parentElement,
      'height',
    );

    const { thead, summary } = defaultTableSizeConfig[size];
    const realHeight =
      thead +
      (isFixedHeight
        ? tbodyHeightInfo.maxHeight
        : tbodyHeightInfo.tbodyHeight) +
      (isShowSummary ? summary : 0);

    dispatch({
      type: UPDATE_STATE,
      payload: {
        style: {
          width,
          height: realHeight < height ? realHeight : height,
          maxHeight,
        },
      },
    });
  };
  const handleBoxShadow = (isShowSummary: boolean) => {
    const ref = isShowSummary ? summaryRef : tbodyRef;
    if (ref.current) {
      const info = ref.current.getValues();
      const { left, right } = columnPinning;
      // 出现滚动条并且左右有固定列时，显示固定列阴影
      const isShowBoxShadow =
        info.clientWidth < info.scrollWidth && !!(left.length || right.length);
      setIsShowBoxShadow(isShowBoxShadow);
    }
  };
  const handleBorderShadow = debounce((scroll: ScrollInfo) => {
    // borderShadow 处理
    const {
      scrollTop,
      scrollLeft,
      scrollWidth,
      scrollHeight,
      clientWidth,
      clientHeight,
    } = scroll;
    const isShowLeftShadow = scrollLeft > 0;
    const isShowRightShadow = scrollLeft + clientWidth < scrollWidth;
    const isShowBottomShadow =
      !isShowSummary && scrollTop + clientHeight < scrollHeight;

    setBorderShadow({
      left: isShowLeftShadow,
      right: isShowRightShadow,
      bottom: isShowBottomShadow,
    });
  }, 1000);
  const dispatchScroll = debounce((scroll: ScrollInfo) => {
    dispatch({
      type: UPDATE_STATE,
      payload: { scroll: { ...tableContextInfo.scroll, ...scroll } },
    });
  }, 1000);
  const handleScrollInfo = (isShowSummary: boolean) => {
    if (tbodyRef.current) {
      const tbodyScroll: ScrollInfo = tbodyRef.current.getValues();
      const scroll: ScrollInfo = { ...initScrollInfo, ...tbodyScroll };

      if (isShowSummary && summaryRef.current) {
        const summaryScroll: ScrollInfo = summaryRef.current.getValues();
        const { scrollLeft, scrollWidth, clientWidth } = summaryScroll;
        Object.assign(scroll, { scrollLeft, scrollWidth, clientWidth });
      }
      return scroll;
    } else {
      return initScrollInfo;
    }
  };

  const tableOptions: TableOptions<T> = useMemo(() => {
    return {
      ...props.tableOptions,
      columns: tableColumns,
      data: dataSource,
      getCoreRowModel: getCoreRowModel(),
    };
  }, [props.tableOptions, tableColumns, dataSource]);

  const table = useReactTable(tableOptions);
  const totalSize = table.getTotalSize();
  const visibleFlatColumns = table.getVisibleFlatColumns(); // 显示的列
  const baseProps = {
    prefixCls,
    columnPinning,
    columnPinningInfo,
    bordered,
    isShowBoxShadow,
  };
  const tableTheadProps = {
    ...baseProps,
    context,
    isCanResizeColumn,
    dragTypes,
    table,
    size,
  };
  const tableTbodyProps = {
    ...baseProps,
    dataSource,
    dragTypes,
    table,
    size,
    context,
  };
  const summaryProps = { ...baseProps, dataSource, columnSummary, size };

  const tableBoxStyle = useMemo(() => {
    const style: React.CSSProperties = { ...props.tableBoxStyle };
    const {
      isCanEdit,
      style: { width = 0 },
    } = tableContextInfo;

    if (isCanEdit && totalSize < (width as number)) {
      style.width = totalSize;
    }
    return style;
  }, [
    props.tableBoxStyle,
    tableContextInfo.style,
    tableContextInfo.isCanEdit,
    totalSize,
  ]);

  React.useImperativeHandle(ref, () => ({
    getTableContext: () => tableContextInfo,
    onResizeTable: onResize,
  }));
  useEffect(() => {
    const payload = {
      id: tableId,
      size,
      columns: visibleFlatColumns,
      columnPinning,
      pagination,
    };
    if (!tableContextInfo.name) Object.assign(payload, { name: DISPLAY_TABLE });
    dispatch({ type: UPDATE_STATE, payload });
  }, [
    tableContextInfo.name,
    tableId,
    visibleFlatColumns,
    columnPinning,
    pagination,
  ]);
  const isDisplayTable = tableContextInfo.name === DISPLAY_TABLE;
  return (
    <div
      id={!isDisplayTable ? tableId : null}
      className={`${prefixCls}-container`}
    >
      <div
        id={isDisplayTable ? tableId : null}
        className={`${prefixCls}-box`}
        style={tableBoxStyle}
      >
        <div
          role="table"
          className={cx(prefixCls, { bordered })}
          style={tableStyle}
        >
          <Scrollbars
            ref={theadRef}
            style={{ height: defaultTableSizeConfig[size].thead }}
            renderTrackHorizontal={(props) => (
              <div
                {...props}
                className={cx({
                  [`${prefixCls}-scrollbar__track--horizontal`]:
                    !dataSource.length,
                })}
              />
            )}
          >
            <TableThead {...tableTheadProps} />
          </Scrollbars>
          <div
            style={{
              height: isFixedHeight
                ? tbodyHeightInfo.maxHeight
                : tbodyHeightInfo.tbodyHeight,
            }}
          >
            <Scrollbars
              ref={tbodyRef}
              style={{ height: tbodyHeightInfo.tbodyHeight }}
              renderTrackVertical={(props) => (
                <div
                  {...props}
                  className={`${prefixCls}-scrollbar__track--vertical`}
                />
              )}
              renderTrackHorizontal={(props) => (
                <div
                  {...props}
                  className={cx({
                    [`${prefixCls}-scrollbar__track--horizontal`]:
                      !isShowSummary,
                  })}
                />
              )}
              onScroll={() => {
                const scroll = handleScrollInfo(isShowSummary);
                theadRef.current.scrollLeft(scroll.scrollLeft);
                handleBorderShadow(scroll);
                dispatchScroll(scroll);
              }}
            >
              {!!dataSource.length ? (
                <TableTbody
                  {...tableTbodyProps}
                  onRow={props.onRow}
                  onCell={props.onCell}
                  onChangeDataSource={props.onChangeDataSource}
                />
              ) : (
                <DefaultStatus ref={defaultStatusRef} {...defaultStatus} />
              )}
            </Scrollbars>
          </div>
          {isShowSummary && !!dataSource.length && (
            <Scrollbars
              ref={summaryRef}
              style={{
                height: defaultTableSizeConfig[size].summary,
                background: 'rgba(240, 170, 20, 0.16)',
                borderBottom: '1px solid #e6eaf5',
              }}
              onScroll={() => {
                const scroll = handleScrollInfo(isShowSummary);
                theadRef.current.scrollLeft(scroll.scrollLeft);
                tbodyRef.current.scrollLeft(scroll.scrollLeft);
                handleBorderShadow(scroll);
                dispatchScroll(scroll);
              }}
            >
              <TableSummary
                {...summaryProps}
                columns={utils.adjustColumnOrder(
                  visibleFlatColumns,
                  columnPinning,
                )}
              />
            </Scrollbars>
          )}
          <div
            className={cx(`${prefixCls}-shadow`, { left: borderShadow.left })}
          ></div>
          <div
            className={cx(`${prefixCls}-shadow`, { right: borderShadow.right })}
          ></div>
          <div
            className={cx(`${prefixCls}-shadow`, {
              bottom: borderShadow.bottom,
            })}
          ></div>
        </div>
      </div>
      {pagination && (
        <TablePagination
          {...pagination}
          onChange={(page, size) => props.onChangePagination?.(page, size)}
        />
      )}
    </div>
  );
}

function BaseTableContext<T extends AnyObject>(
  props: Props<T>,
  ref?: React.ForwardedRef<TableContextRef<T>>,
) {
  return <TableContext {...props} Table={BaseTable} ref={ref} />;
}

export default React.forwardRef(BaseTableContext) as <T>(
  props: Props<T> & { ref?: React.ForwardedRef<TableContextRef<T>> },
) => ReturnType<typeof BaseTableContext>;
